# -*- coding: utf-8 -*-
# -*- frozen_string_literal: true -*-
require "sdbm"

class Lorca
  # Built-in Phrases expansion. Adds all necessary dependencies to enable secure
  # random phrases. Intended as a passphrase generator.
  #
  # It exposes the following instance methods:
  #
  # phrase :: A phrase randomly generated suitable as a passphrase.
  module Phrases
    # Default minimum number of words a user can request per phrase.
    MIN_WORDS = 8.freeze
    # Default maximum number of words a user can request per phrase.
    MAX_WORDS = 53.freeze
    # Default number of dice used for rolling a word id. Meant to fit the included
    # vocabulary.
    DICE = 5.freeze
    # The default vocabulary's database path.
    VOCABULARY = "#{__dir__}/phrases/eff_large_wordlist"

    # Configure the Lorca::Phrases expansion upon loading.
    #
    # The following settings are configurable.
    #
    # min_words :: The minimum number of words a user can request per phrase.
    # max_words :: The maximum number of words a user can request per phrase.
    # dice_to_toss :: The number of dice to toss to generate a word id. It should
    #                 only be changed when switching vocabulary.
    # vocabulary :: The path to the SDBM compatible vocabulary database.
    def self.configure expansion, min_words: MIN_WORDS, max_words: MAX_WORDS,
        dice_to_toss: DICE, vocabulary: VOCABULARY
      config = expansion.settings[:phrases] = {}
      config[:min_words] = min_words
      config[:max_words] = max_words
      config[:dice_to_toss] = dice_to_toss
      config[:vocabulary] = vocabulary
    end

    module LorcaPlugin
      # The phrase written given a number of +words+. Defaults to
      # Phrases::MIN_WORDS
      def phrase words: nil
        conf = settings[:phrases]
        words = words ? words : conf[:min_words]
        words = validate_counter words, range: conf[:min_words]..conf[:max_words]
        phrase_ids = []
        words.times { phrase_ids.push(roll_word_id dice: conf[:dice_to_toss]) }
        phrase_ids.map(&method(:word)).join " "
      end

      # :nodoc:
      private

      WordIdError = Class.new StandardError
      private_constant :WordIdError

      def word id, wordlist: settings[:phrases][:vocabulary], manager: SDBM
        read_only = 0444
        manager.open(wordlist, read_only) { |db| db.fetch id }
      rescue => e
        raise WordIdError, e.message
      end
    end
  end
end
